package top.techqi.app.mcs51.usbdlk.vm

import android.content.Context
import android.graphics.Color
import android.hardware.usb.UsbDevice
import android.hardware.usb.UsbManager
import android.text.SpannableStringBuilder
import android.text.style.ForegroundColorSpan
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import top.techqi.core.App
import top.techqi.usbd.UsbProber
import top.techqi.usbd.driver.serial.BasicsSerialDriver
import top.techqi.usbd.kext.new
import java.util.TreeMap

private typealias StreamKey = Pair<Long, Boolean>
private typealias StreamVal = Triple<List<Byte>, CharSequence, CharSequence>

class UsbdlkUartViewModel : ViewModel() {
    private val usbManager = App.INSTANCE.getSystemService(Context.USB_SERVICE) as UsbManager
    private lateinit var usbDriver: BasicsSerialDriver

    @Volatile
    private var loop = true
    private var thread: Thread? = null

    private val queue = TreeMap<StreamKey, StreamVal> { o1, o2 ->
        o1.first.compareTo(o2.first)
    }

    private val runnable = Runnable {
        val buffer = ByteArray(256)
        while (loop) {
            val size = usbDriver.read(buffer, buffer.size, 0)
            recv(buffer, size)
            Thread.sleep(INTERVAL_DOZE)
        }
    }

    val streamData = MutableLiveData<Map<StreamKey, StreamVal>>()

    fun openDevice(device: UsbDevice, intfId: Int): Boolean {
        val (_, driverCls) = UsbProber.detectDrivers(device).get(intfId) ?: return false
        usbDriver = driverCls.new(device, intfId) as? BasicsSerialDriver ?: return false
        val conn = usbManager.openDevice(device) ?: return false
        usbDriver.open(conn)
        thread = Thread(runnable)
        thread?.start()
        return true
    }

    override fun onCleared() {
        loop = false

        if (::usbDriver.isInitialized) {
            usbDriver.close()
        }
        super.onCleared()
    }

    fun send(buffer: ByteArray) {
        if (buffer.isEmpty()) return

        val time = System.currentTimeMillis()
        val data = buffer.toList()
        val (hex, asc) = makeNormalization(data)
        queue[time to true] = Triple(data, hex, asc)

        usbDriver.write(buffer, buffer.size, 0)
        streamData.postValue(queue)
    }

    fun recv(buffer: ByteArray, size: Int) {
        if (size <= 0) return

        var time = System.currentTimeMillis()
        val data: MutableList<Byte>
        val entry = queue.lastEntry()
        if (entry != null && !entry.key.second && time - entry.key.first < INTERVAL_COMB) {
            time = entry.key.first
            data = entry.value.first as MutableList<Byte>
        } else {
            data = ArrayList()
        }
        for (i in 0 until size) data.add(buffer[i])

        val (hex, asc) = makeNormalization(data)
        queue[time to false] = Triple(ArrayList(data), hex, asc)

        Runtime.getRuntime().gc()
        streamData.postValue(queue)
    }

    private fun makeNormalization(data: List<Byte>): Pair<CharSequence, CharSequence> {
        val builderA = SpannableStringBuilder()
        val builderB = SpannableStringBuilder()
        data.forEachIndexed { index, byte ->
            val hsv = floatArrayOf(byte * 360f / 255, 1f, 1f)
            val byteL = byte.toInt() and 0x0F
            if (byteL < 8) {
                hsv[1] = 1f - byteL / 16f
            } else {
                hsv[2] = 1f - (byteL - 8) / 16f
            }
            val color = Color.HSVToColor(hsv)
            val span = ForegroundColorSpan(color)

            builderA.append(
                "%1$02X".format(byte),
                span,
                SpannableStringBuilder.SPAN_INCLUSIVE_EXCLUSIVE
            )
            builderB.append(
                if (byte < 0x20) "·" else byte.toInt().toChar().toString(),
                span,
                SpannableStringBuilder.SPAN_INCLUSIVE_EXCLUSIVE
            )
            if (index % 8 == 7 && index != data.lastIndex) {
                builderA.append("\n")
                builderB.append("\n")
            } else {
                builderA.append(" ")
            }
        }
        return Pair(builderA, builderB)
    }

    companion object{
        const val INTERVAL_COMB = 2000L
        const val INTERVAL_DOZE = 100L
    }
}
